using System.Collections.Generic;
using System.Collections;
using Commons.Injector;
using System;
using Commons.Utils;
using Commons.Preferences;


namespace Commons.Loading
{
	public class LoadingContainerTask : ILoadingContainerTask
	{
		private const float MAX_FRAME_WORK = 0.25f; // 250ms
		private CoroutineRunner cRunner;
		private int completedTasks;
		private bool error;
		private List<string> runningTasks;
		public List<string> RunningTasks { get{return runningTasks; }}
		
		public List<string> Required
		{
			get; 
			set; 
		}
		
		public event Action<object, EventArgs> Complete;


		Dictionary<string, BasicLoadingTask> tasks;
		protected float currentAsyncWeight = 0;
		protected TaskCompletedCallback callback;

		public string Name { get; protected set; }

		public bool IsAsync
		{
			get { return true; }
		}

		public float Weight { get; protected set; }

		private Queue<KeyValuePair<string, Dictionary<string, object>>> eventQueue;
		
		
		public LoadingContainerTask(string name, TaskCompletedCallback callback)
		{
			Name = name;
			this.callback = callback;
			tasks = new Dictionary<string, BasicLoadingTask>();
			runningTasks = new List<string>();
		}

		public ILoadingTask AddTask(string taskId, BasicLoadingTask newTask)
		{
			tasks.Add(taskId, newTask);
			if (newTask.IsAsync)
				Weight += newTask.Weight;

			newTask.Container = this;

			return tasks[taskId];
		}
		
		public void UnlockTasks(List<string> ids)
		{
			foreach(string name in ids)
			{
				BasicLoadingTask task = tasks[name];
				if(task == null)
				{
					Debug.LogWarning("Cannot unlock task "+name+". Not found");
					continue;
				}

				if(!task.Unlocked)
				{
					Debug.Log("Unlocking step "+name);
					task.Unlock();
				}
			}
		}
	
		public int NumChildren 
		{ 
			get
			{
				return tasks.Count;
			}
		}
		
		public virtual void SetUp ()
		{
			error = false;
		}
		
		public virtual void TearDown ()
		{
		}
		
		
		private float startTime;
		private float lastTimeRef;
		public void Start()
		{
			cRunner = new CoroutineRunner();

			OnBegin();
			SetUp();
			StartStartable();
		}

		public void Stop()
		{
			cRunner.StopCoroutine("StartTask");

			runningTasks.Clear();

			tasks.Clear();
		}

		public IEnumerator Update()
		{
			return null;
		}
		
		protected void OnTaskBegin(BasicLoadingTask task)
		{
			trackEvent("loading", new Dictionary<string, object> { {"Container", Name}, {"Action", "taskBegin"},{"Task",task.GetType().Name} });
		}

		/**
		 * To process after each task has completelly finished processing
		 * task
		 * taskTime	in milliseconds
		 * */
		protected void OnTaskEnd(BasicLoadingTask task)
		{
			RemoveRunningTask(task);
			completedTasks++;
		}

		protected void OnBegin()
		{
			trackEvent("loading", new Dictionary<string, object> { {"Container", Name}, {"Action", "begin"} });
		}

		protected void OnEnd()
		{
			if (Complete != null)
			{
				Complete(this, EventArgs.Empty);
			}
		}

		protected void trackEvent(string source, Dictionary<string, object> values = null)
		{
			// create the queue if there is nothing
			if (eventQueue == null)
			{
				eventQueue = new Queue<KeyValuePair<string, Dictionary<string, object>>>();
			}

			eventQueue.Enqueue(new KeyValuePair<string, Dictionary<string, object>>(source, values));
		}

		protected void flushEvents()
		{
			
			foreach(KeyValuePair<string, Dictionary<string, object>> obj in eventQueue)
			{
				//EventService.trackEvent(obj.Key, obj.Value);
			}
			// release the queue
			eventQueue = null;
		}
		
		public float Progress 
		{ 
			get
			{
				if(NumChildren == 0 || completedTasks < 0)
					return 0.0f;
				if( completedTasks >= NumChildren || Weight == 0)
					return 1.0f;

				float totalProgress = 0;
				float totalWeight = 0;
				foreach(ILoadingTask task in tasks.Values)
				{
					totalProgress += task.Weight * (task.Started ? task.Progress : 0);
					totalWeight += task.Weight;
				}
				//return (currentAsyncWeight + (task.IsAsync ? (task.Weight * task.Progress) : 0.0f)) / Weight;
				float progress = totalProgress / totalWeight;
				return progress >= 0 ? progress : 0f;
			} 
		}
		
		public bool Completed 
		{
			get;
			set;
		}
		
		
		
		/*****************************************/
		/*****************************************/
		/*****************************************/
		
		private const int STATE_NONE = 0;
		private const int STATE_START = 1;
		private const int STATE_HALT = 2;
		private const int STATE_COMPLETE = 3;
		
		private bool halt = false;
		private bool starting = false;

		
		protected int GetOverallState()
		{
			/*
			 * STEP logic
			 *				started		completed		halted
			 *              ====================================
			 * NONE			false		false			false
			 * STARTED		true		false			false
			 * COMPLETED	true		true			false
			 * HALTED		true		false			true
			 * PENDING		X			false			false
			 *
			 * SEQUENCE logic
			 *				anyRunning	anyCompleted	anyHalted	anyPending
			 * NONE			false		false			false		false
			 * STARTED		true		X				X			true
			 * HALTED		false		X				true		X
			 * COMPLETED	false		true			false		false
			 */

			bool anyPending = false;
			bool anyRunning = false;
			bool anyHalted = false;
			bool anyCompleted = false;

			// trace("getOverallState ...");
			foreach (BasicLoadingTask task in tasks.Values)
			{
				// trace("task "+task.name+" started: "+task.started+" completed: "+task.completed+" halted: "+task.halted);
				if(task.Started)
				{
					// HALTED
					if(task.Halted)
						anyHalted = true;
					// COMPLETED
					else if(task.Completed)
						anyCompleted = true;
					//PENDING
					else
						anyRunning = true;
				}
				else
				{
					anyPending = true;
				}
				// else NONE
			}

			// trace("anyStarted "+anyStarted+" anyCompleted "+anyCompleted+" anyHalted "+anyHalted);

			if(anyHalted && !anyRunning) return STATE_HALT;
			if(anyRunning || anyPending) return STATE_START;
			if(anyCompleted) return STATE_COMPLETE;

			return STATE_NONE;
		}
		
		protected void StartStartable()
		{
			// if halted, it's done
			int overallState = GetOverallState();


			List<BasicLoadingTask> startable = GetStartableTasks();
			// TODO: check loops and other diseases
			if(startable.Count == 0)
			{
				if(Completed)
				{
					Debug.LogWarning("sequence completed - ignoring");
					return;
				}
				switch(overallState)
				{
					// all finished
					case STATE_COMPLETE:
						Completed = true;
						OnCompleted();
						break;
					// empty seq
					case STATE_NONE:
						Completed = true;
						//_callback.SequenceComplete(this);
						break;
					// some halted but none running
					case STATE_HALT:
						Completed = true;
						//_callback.SequenceHalt(this);
						break;
					// other cases:
					// * some started => still working, should wait
					// * some none, some finished, but none started => stalled. configuration error
					default:
						// TODO: here it might be working or stalled
						break;
				}
				return;
			}


			starting = true;
			foreach(BasicLoadingTask task in startable)
			{
				task.Start();
				
			}
			foreach(BasicLoadingTask task in startable)
			{
				Debug.Log("START TASK " + task.Name);
				cRunner.Execute(StartTask(task));
			}
			starting = false;

			/*if(halt)
			{
				if(overallState == STATE_HALT)
				{
					_callback.SequenceHalt(this);
				}
				return;
			}*/
		}
		
		
		private IEnumerable<object> StartTask(BasicLoadingTask task)
		{
			AddRunningTask(task);

			OnTaskBegin(task);

			task.SetUp();

			IEnumerator updateEnum = CollectionsHelper.SafeEnumerator(task.Update(), (e) => HandleException(e, task));
			while (!error && updateEnum.MoveNext())
			{
				yield return updateEnum.Current;
			}

			task.TearDown();

			if (!error)
			{
                OnTaskEnd(task);

				task.Completed = true;
				Debug.Log("END TASK " + task.Name);

				if(task.Callback != null)
				{
					task.Callback();
				}
			
				Debug.Log("Update complete num: "+task.Name);
				StartStartable();
			}
		}


		private void HandleException(Exception exception, BasicLoadingTask task)
		{
			Debug.LogException(exception);

			string stackTrace = exception.StackTrace;
			string safeStackTrace = stackTrace.Replace('"', '\'').Replace('\n', ' ').Replace('\r', ' ');

			trackEvent("error", new Dictionary<string, object> { {"Container", Name}, {"Action", "loading"}, {"Message", exception.Message}, {"Task",task}, {"StackTrace", safeStackTrace} });

			error = true;

		}

		protected List<BasicLoadingTask> GetStartableTasks()
		{
			List<BasicLoadingTask> res = new List<BasicLoadingTask>();
			
			bool continueOuterLoop  = false;
			foreach(string name in tasks.Keys)
			{
				BasicLoadingTask task = tasks[name];
				if(task.Started) continue; // skip if it's already started

				// check dependencies
				if(task.Required != null)
				{
					foreach(string depName in task.Required)
					{
						
						BasicLoadingTask dep = tasks[depName];
						if(dep == null)
						{
							Debug.LogWarning("Cannot find dependency '"+depName+"' of step '"+name+"'");
						}
						// if dep is not satisfied, skip
						if(! dep.Completed && ! dep.Unlocked) 
						{
							continueOuterLoop = true;
							break;
						}
					}
				}
				if(continueOuterLoop)
				{
					continueOuterLoop = false;
					continue;
				}
				res.Add(task);
			}

			return res;
		}
		
		public virtual BasicLoadingTask Requires(params string[] requires)
		{
			return null;
		}
		
		public virtual BasicLoadingTask OnComplete(TaskCompletedCallback callback)
		{
			return null;
		}
		
		private void OnCompleted()
		{
			TearDown();
			runningTasks.Clear();
			OnEnd();
			
			Completed = true;
			if(callback != null)
				callback();
		}
		
		private void AddRunningTask(BasicLoadingTask task)
		{
			runningTasks.Add(task.GetType().Name);
		}
		private void RemoveRunningTask(BasicLoadingTask task)
		{
			runningTasks.Remove(task.GetType().Name);
		}
		
	}
}